﻿using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using UnityEngine;
using DataSystem;
using Utility.PoolSystem;
using Utility.Math;
using Utility;

using TileGroupRef = Utility.PoolSystem.UPRef<TerrainSystem.TileGroup>;
using TileRef = Utility.PoolSystem.UPRef<TerrainSystem.Tile>;
using Newtonsoft.Json;
using System.Runtime.Serialization;
using Utility.DataSystem;

namespace TerrainSystem
{
	public class TileGroup : MonoBehaviour, IPoolCallback, IBuildable
	{
		#region static
		public class SaveData
		{
			public Vector2Int coord;
			public Rotation4 orientation;
			public string settingName;
			public bool isNewAdded;
			public SaveData ()
			{
			}

			public SaveData (TileGroup tilegroup)
			{
				coord = tilegroup.Coord;
				orientation = tilegroup.Orientation;
				settingName = tilegroup.Setting.SettingName;
				isNewAdded = Main.Terrain.Data.NewAddedBuildable.Contains (tilegroup);
			}
		}

		static GameObject _prefab;
		static GameObject Prefab
		{
			get
			{
				if (_prefab == null)
				{
					_prefab = new GameObject ("TileGroupPrefab");
					DontDestroyOnLoad (_prefab);
					_prefab.hideFlags = HideFlags.HideInHierarchy;
					_prefab.AddComponent<TileGroup> ();
					_prefab.SetActive (false);
				}
				return _prefab;
			}
		}

		public static TileGroupRef Spawn (TileGroupSetting setting)
		{
			if (setting == null)
			{
				return null;
			}

			TileGroupRef groupRef = PoolManager.Spawn (Prefab).GetComponent<TileGroup> ();
			TileGroup group = groupRef.Component;
			group.UPRef = groupRef;
			group.Setting = setting;
			group.Orientation = default;

			group.gameObject.name = setting.SettingName;

			//tile
			foreach (var pair in setting.TileSettings)
			{
				var tileRef = Tile.Spawn (pair.Value);
				if (!group.TryAddTile (pair.Key, tileRef))
				{
					tileRef.Despawn ();
				}
			}
			return groupRef;
		}

		public static TileGroupRef Spawn (SaveData data)
		{
			var setting = SettingCenter.GetSetting<TileGroupSetting> (data.settingName);
			if (setting == null)
			{
				return null;
			}

			TileGroupRef groupRef = PoolManager.Spawn (Prefab).GetComponent<TileGroup> ();
			TileGroup group = groupRef.Component;
			group.UPRef = groupRef;
			group.Setting = setting;
			group.Coord = data.coord;
			group.Orientation = data.orientation;

			group.gameObject.name = setting.SettingName;

			//tile
			foreach (var pair in setting.TileSettings)
			{
				var tileRef = Tile.Spawn (pair.Value);
				if (!group.TryAddTile (pair.Key, tileRef))
				{
					tileRef.Despawn ();
				}
			}

			return groupRef;
		}

		public static void SetTerrainObject (TileGroupRef tileGroupRef, TerrainObject terrainObject, Vector2Int groupCoord)
		{
			var tileGroup = tileGroupRef.Component;
			if (tileGroup)
			{
				tileGroup.TerrainObject = terrainObject;
				tileGroup.Coord = groupCoord;
				tileGroup.transform.SetParent (terrainObject?.transform);
				tileGroup.transform.localPosition = new Vector3 (groupCoord.x, 0, groupCoord.y);
			}
		}
		#endregion
		#region pool callbacks
		void IPoolCallback.OnSpawn ()
		{
			Orientation = default;
		}

		void IPoolCallback.OnDespawn ()
		{
			foreach (var tileRef in tiles.Values)
			{
				tileRef.Despawn ();
			}
			tiles.Clear ();
		}
		#endregion
		#region IBuildable
		void IBuildable.Despawn ()
		{
			UPRef.Despawn ();
		}

		void IBuildable.UpdatePosition (TerrainObject terrain, Vector2Int? coord)
		{
			if (coord.HasValue)
			{
				gameObject.SetActive (true);
				transform.position = terrain.CoordToPosition (coord.Value);
			}
			else
			{
				gameObject.SetActive (false);
			}
		}

		void IBuildable.Rotate (int rotation)
		{
			Orientation += Rotation4.Right * rotation;
		}

		void IBuildable.Flip ()
		{
			Main.Game.Construction.Build_End ();
			Main.Game.Construction.Build_Start (Setting.bilateralSymmetry_half);
		}
		
		bool IBuildable.TryPlace (TerrainObject terrain, Vector2Int? coord)
		{
			if (coord.HasValue)
			{
				var result = terrain.Data.TryAddTileGroup (coord.Value, UPRef, true);
				if (result)
				{
				}
				return result;
			}
			return false;
		}

		bool IBuildable.CanViewPlace (TerrainObject terrain, Vector2Int? coord)
		{
			if (coord.HasValue)
			{
				foreach (var localCoord in tiles.Keys)
				{
					var tileCoord = localCoord.LocalToWorldCoord (coord.Value, Orientation);
					if (!terrain.Data.IsInMap (tileCoord))
					{
						return false;
					}
				}
				return true;
			}
			return false;
		}
		#endregion
		#region  transform anchor
		Transform _transform_tiles;
		public Transform Transform_Tiles
		{
			get
			{
				if (_transform_tiles == null)
				{
					GameObject go_tiles = new GameObject ("Tiles");
					go_tiles.transform.SetParent (transform, false);
					_transform_tiles = go_tiles.transform;
				}
				return _transform_tiles;
			}
		}

		Transform _transform_buildings;
		public Transform Transform_Buildings
		{
			get
			{
				if (_transform_buildings == null)
				{
					GameObject go_buildings = new GameObject ("Buildings");
					go_buildings.transform.SetParent (transform, false);
					_transform_buildings = go_buildings.transform;
				}
				return _transform_buildings;
			}
		}
		#endregion

		public TileGroupSetting Setting { get; private set; }
		public bool CanCompleteOverlap => Setting.CanCompleteOverlap;
		public TileGroupSetting.OverlapTypes OverlapType => Setting.OverlapType;
		public TileGroupSetting.BeOverlappedTypes BeOverlappedTypes => Setting.BeOverlappedType;

		public TerrainObject TerrainObject { get; private set; }


		Dictionary<Vector2Int, TileRef> tiles = new Dictionary<Vector2Int, TileRef> ();
		public IEnumerable<TileRef> Tiles => tiles.Values;

		public Vector2Int Coord { get; private set; }

		Rotation4 _orientation = default;
		public Rotation4 Orientation
		{
			get => _orientation;
			set
			{
				_orientation = value;
				//rotate tiles
				transform.eulerAngles = new Vector3 (0, value.Angle, 0);
			}
		}

		public TileGroupRef UPRef { get; private set; }

		public Vector2Int WorldToLocalCoord (Vector2Int worldCoord)
		{
			return worldCoord.WorldToLocalCoord (Coord, Orientation);
		}

		public Vector2Int LocalToWorldCoord (Vector2Int localCoord)
		{
			return localCoord.LocalToWorldCoord (Coord, Orientation);
		}

		#region tile
		public bool CanAddTile (Vector2Int localCoord)
		{
			return !(tiles.TryGetValue (localCoord, out var t) && t);
		}

		public void AddTile (Vector2Int localCoord, TileRef tileRef)
		{
			if (tileRef)
			{
				if (tiles.ContainsKey (localCoord))
				{
					tiles [localCoord].Despawn ();
					tiles.Remove (localCoord);
				}
				
				var tile = tileRef.Component;
				Tile.SetTileGroup (tileRef, UPRef, localCoord);
				tiles.Add (localCoord, tileRef);
			}
		}

		public void RemoveTile (Vector2Int localCoord)
		{
			if (tiles.ContainsKey (localCoord))
			{
				tiles[localCoord].Despawn ();
				tiles.Remove (localCoord);
			}
		}

		public bool TryAddTile (Vector2Int localCoord, TileRef tileRef)
		{
			if (CanAddTile (localCoord))
			{
				AddTile (localCoord, tileRef);
				return true;
			}
			return false;
		}

		public bool TrySpawnTile (Vector2Int coord, out TileRef tileRef)
		{
			tileRef = default;
			var localCoord = WorldToLocalCoord (coord);
			var tileSetting = Setting.TileSettings.TryGetValue (localCoord);
			if (tileSetting != null)
			{
				if (TerrainObject.Data.GetTile (tileSetting.TerrainLayer, coord))
				{
					return false;
				}

				tileRef = Tile.Spawn (tileSetting);
				AddTile (localCoord, tileRef);
				return true;
			}
			return false;
		}

		public void RemoveTile (TileRef tileRef)
		{
			if (tileRef)
			{
				var tile = tileRef.Component;
				var localCoord = tile.LocalCoord;
				if (GetTile (localCoord) == tileRef)
				{
					Tile.SetTileGroup (tileRef, null, default);
					tiles.Remove (localCoord);
				}
			}
		}

		public TileRef GetTile (Vector2Int localCoord)
		{
			return tiles.TryGetValue (localCoord, out var tileRef) ? tileRef : null;
		}

		public float GetSurfaceHeight (Vector2Int localCoord)
		{
			return GetTile (localCoord).Component?.SurfaceHeight ?? 0;
		}

		public void CleanUpRefs_Tile ()
		{
			bool NotValid (Vector2Int key, TileRef value) => !value;
			tiles.RemoveAll (NotValid);
		}
		#endregion

		//public bool TriggerAble => Setting.TriggerEliteBattle;
		public bool TriggerAble => false;
		public void OnConnected ()
		{
		}
	}
}